﻿using System;
using System.Collections.Generic;
using System.Configuration;
using ControleRepresentante.Data.Catalogs;
using FluentNHibernate.Cfg;
using FluentNHibernate.Conventions.Helpers;
using NHibernate;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IRepositoryProvider : IDisposable
    {
        T GetRepository<T>(params object[] repositoryParameters) where T : IRepository;
    }

    public static class RepositoryProvider
    {
        public static void RegisterControleRepresentanteRepositories(RepositoryProvider<ControleRepresentanteCatalog> repositoryProvider)
        {
            repositoryProvider.RegisterRepository<IAttributeRepository, AttributeRepository>();
            repositoryProvider.RegisterRepository<IClienteRepository, ClienteRepository>();
            repositoryProvider.RegisterRepository<IFabricaRepository, FabricaRepository>();
            repositoryProvider.RegisterRepository<IPedidoRepository, PedidoRepository>();
            repositoryProvider.RegisterRepository<IProdutoRepository, ProdutoRepository>();
            repositoryProvider.RegisterRepository<IRepresentanteRepository, RepresentanteRepository>();
            repositoryProvider.RegisterRepository<ITransportadoraRepository, TransportadoraRepository>();
            repositoryProvider.RegisterRepository<IUserRepository, UserRepository>();
            repositoryProvider.RegisterRepository<IVendedorRepository, VendedorRepository>();
            repositoryProvider.RegisterRepository<IRelatorioVisitaRepository, RelatorioVisitaRepository>();
        }
    }

    public class RepositoryProvider<TCatalog> : IRepositoryProvider
        where TCatalog : ICatalog
    {
        private ISession m_Session;
        private readonly IRepositoryProviderConfiguration m_RepositoryProviderConfiguration;
        private readonly Dictionary<Type, IRepositoryCreator> m_RepositoryCreatorDictionary;

        public RepositoryProvider(IRepositoryProviderConfiguration repositoryProviderConfiguration)
        {
            m_RepositoryProviderConfiguration = repositoryProviderConfiguration;
            m_RepositoryCreatorDictionary = new Dictionary<Type, IRepositoryCreator>();
        }

        public void RegisterRepository<TIRepository, TRepository>()
            where TIRepository : IRepository
            where TRepository : class, TIRepository
        {
            m_RepositoryCreatorDictionary.Add(typeof(TIRepository), new RepositoryCreator<TRepository>());
        }

        public void RegisterRepository<TRepository>(TRepository repository)
            where TRepository : class, IRepository
        {
            m_RepositoryCreatorDictionary.Add(typeof(TRepository), new RepositoryCreator<TRepository>(repository));
        }

        private ISession Session
        {
            get
            {
                if (m_Session == null)
                {
                    TCatalog catalog = (TCatalog)Activator.CreateInstance(typeof(TCatalog));
                    ISessionFactory sessionFactory = CreateSessionFactory(catalog);
                    m_Session = sessionFactory.OpenSession();

                    bool checkCatalog;
                    if (!Boolean.TryParse(ConfigurationManager.AppSettings["CheckCatalog"], out checkCatalog))
                    {
                        checkCatalog = true;
                    }
                    catalog.Initialize(m_Session.Connection, checkCatalog);
                }
                return m_Session;
            }
        }

        private ISessionFactory CreateSessionFactory(ICatalog catalog)
        {
            NHibernate.Cfg.Environment.UseReflectionOptimizer = false;
            ISessionFactory buildSessionFactory = Fluently.Configure()
                .Database(m_RepositoryProviderConfiguration.PersistenceConfigurerGenerator(catalog))
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<IRepository>()
                                   .Conventions.Add(
                                       PrimaryKey.Name.Is(x => "Id"),
                                       ForeignKey.EndsWith("Id"),
                                       Table.Is(cls => cls.EntityType.Name)))
                .ExposeConfiguration(m_RepositoryProviderConfiguration.ExposeConfiguration)
                .BuildSessionFactory();
            return buildSessionFactory;
        }
        
        public virtual T GetRepository<T>(params object[] repositoryParameters)
            where T : IRepository
        {
            IRepositoryCreator repositoryCreator;
            if (!m_RepositoryCreatorDictionary.TryGetValue(typeof(T), out repositoryCreator))
            {
                throw new RepositoryNotRegisteredException<T>();
            }
            IRepository repository = repositoryCreator.Create(m_RepositoryProviderConfiguration, Session, repositoryParameters);
            if (!(repository is T))
            {
                throw new RepositoryTypeMismatchException<T>(repository);
            }
            return (T)repository;
        }

        public virtual void Dispose()
        {
            if (m_Session != null)
            {
                m_Session.Clear();
                m_Session.Disconnect();
                m_Session.Close();
                m_Session.Dispose();
            }
        }
    }

    class RepositoryTypeMismatchException<T> : Exception
    {
        private readonly IRepository m_Repository;

        public RepositoryTypeMismatchException(IRepository repository)
        {
            m_Repository = repository;
        }

        public override string Message
        {
            get
            {
                return String.Format("Repository of type {0} doesn't match with type {1}.", m_Repository.GetType().FullName, typeof(T).FullName);
            }
        }
    }

    class RepositoryNotRegisteredException<T> : Exception
    where T : IRepository
    {
        public override string Message
        {
            get
            {
                return String.Format("Repository for type {0} not registered.", typeof(T).FullName);
            }
        }
    }
}